/*
 * Copyright (c) 2011, Nikolaus Moll
 * All rights reserved.
 * 
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions are met:
 * * Redistributions of source code must retain the above copyright
 *   notice, this list of conditions and the following disclaimer.
 * * Redistributions in binary form must reproduce the above copyright
 *   notice, this list of conditions and the following disclaimer in the
 *   documentation and/or other materials provided with the distribution.
 * * Neither the name of the gradecalculator.net nor the
 *   names of its contributors may be used to endorse or promote products
 *   derived from this software without specific prior written permission.
 * 
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
 * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
 * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY
 * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
 * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
 * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
 * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
 * LIABILITY, OR TORT(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH
 * DAMAGE.
 */
package net.gradecalculator.plugin.core.stux;

import java.util.HashMap;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;

import net.gradecalculator.core.GCCore;
import net.gradecalculator.core.enums.PassResult;
import net.gradecalculator.core.interfaces.ICoreFactory;
import net.gradecalculator.core.interfaces.ICourse;
import net.gradecalculator.core.interfaces.ICourseType;
import net.gradecalculator.core.interfaces.IModuleList;
import net.gradecalculator.core.interfaces.IPerformanceType;
import net.gradecalculator.core.interfaces.IRegulationsItem;
import net.gradecalculator.core.interfaces.IStudent;
import net.gradecalculator.core.interfaces.IStudyRegulations;
import net.gradecalculator.core.interfaces.results.IResult;
import net.gradecalculator.core.interfaces.results.IResultType;
import net.gradecalculator.core.util.PerformanceListUtil;
import net.gradecalculator.core.util.ResultTypeListUtil;
import net.gradecalculator.plugin.core.interfaces.IStudentPlugin;
import net.gradecalculator.plugin.core.stux.internal.XmlExaminationResult;
import net.gradecalculator.plugin.core.stux.internal.XmlStudent;
import net.gradecalculator.plugin.core.stux.internal.enums.XmlCourseTypeDescriptor;
import net.gradecalculator.plugin.core.stux.internal.enums.XmlExaminationTypeDescriptor;
import net.gradecalculator.plugin.loader.exceptions.PluginException;

public class StuxPlugin implements IStudentPlugin {
	private final Map<String, List<String>> extensions;
	
	private String root = System.getenv("user.dir");

	private ICoreFactory factory;
	
	@SuppressWarnings("serial")
	public StuxPlugin()
	{
		this.extensions = new HashMap<String, List<String>>();
		extensions.put("Student regulations file", new LinkedList<String>() {{ add("stux"); }});
	}
	
	private String getStudentFilename(final String student)
	{
		return student;
	}
	
//	@Override
//	public List<String> getLoadableFileExtension()
//	{
//		return extensions;
//	}
//	
//	@Override
//	public List<String> getWriteableFileExtension()
//	{
//		return extensions;
//	}
	
	@Override
	public void setCoreFactory(final ICoreFactory factory) {
		this.factory = factory;
	}

	@Override
	public IStudent readStudent(final String studentName) {
		try {
			final IStudent result = factory.createStudent();
			final XmlStudent student = XmlStudent.createFromXML(getStudentFilename(studentName));
			
			result.setName(student.getName());
			result.setUniversity(GCCore.readUniversity(student.getUniversityName()));
			result.setCourseOfStudies(student.getCourseOfStudies());
			result.setDegree(student.getDegree());
			result.setVersionOfRegulations(student.getSemester());
			
			result.setResultTypes(createResultTypes());
			
			result.setRegulations(GCCore.readRegulations(result));
			
			result.setElectiveModulesAreas(convertElectiveModuleAreas(student.getSelectedModuleLists(), result.getRegulations()));
			result.setResults(convert(student.getResults(), result.getRegulations(), result.getResultTypes()));
		
			return result;
		}
		catch (final Exception e) {
			throw new PluginException("Cannot read student", e);
		}
	}
	
	private List<IModuleList> convertElectiveModuleAreas(final 
			List<String> selectedModuleLists, final IStudyRegulations regulations) {
		final List<IModuleList> result = new LinkedList<IModuleList>();
		for (String identifier : selectedModuleLists) {
			final IRegulationsItem item = searchItem(regulations.getModuleList(), identifier);
			if (item == null) {
				continue;
			}
			if (item instanceof IModuleList) {
				result.add((IModuleList)item);
			}
		}
		return result;
	}

	private List<IResultType> createResultTypes() {
		final List<IResultType> result = new LinkedList<IResultType>();
		GCCore.getFactory().createResultType(result, "PASSED");
		GCCore.getFactory().createResultType(result, "ESTIMATED");
		GCCore.getFactory().createResultType(result, "BESTCASE");
		GCCore.getFactory().createResultType(result, "WORSTCASE");
		
		return result;
	}

	// need student here because of ResultTypes...
	private static Map<IResultType, String> convert(final Map<String, String> grades, final IStudyRegulations regulations, final List<IResultType> resultTypes) {
		final Map<IResultType, String> result = new HashMap<IResultType, String>();
		for (String gradeType : grades.keySet()) {
			final IResultType type = ResultTypeListUtil.valueOf(gradeType, resultTypes); 
			if (type == null) {
				throw new IllegalStateException("Cannot detrmine result type " + gradeType);
			}
			
			result.put(type, grades.get(gradeType));
		}
		return result;
	}
	
	private static String getIdentificator(final IRegulationsItem item) {
		if (item.getParent() != null) {
			return getIdentificator(item.getParent()) + "." + item.getName();
		} else {
			return item.getName();
		}
	}
	
	private static IRegulationsItem searchItem(final IRegulationsItem root, final String identificator) {
		final String base = getIdentificator(root);

		if (!identificator.startsWith(base)) {
			return null;
		}

		if (identificator.equals(base)) { 
			return root;
		}
		
		for (IRegulationsItem child : root.getChildren()) {
			final IRegulationsItem item = searchItem(child, identificator);
			if (item != null) {
				return item;
			}
		}
		
		return root;
	}

	private static IResult convert(final XmlExaminationResult xmlResult, final IStudyRegulations regulations, final List<IResultType> resultTypes) {
		final IResult result = GCCore.getFactory().createResult();
		
		result.setExamNumber(xmlResult.getExamNumber());
		result.setSemester(xmlResult.getSemester());
		result.setGrades(convert(xmlResult.getGrades(), regulations, resultTypes));
		result.setUngradedPassed(convertPractical(xmlResult.getUngradedPassed()));
		
		final IRegulationsItem courseItem = searchItem(regulations.getModuleList(), xmlResult.getCourseIdentifier());
		if (courseItem instanceof ICourse) {
			result.setCourse((ICourse)courseItem);
		}
		
		return result;
	}
	
	private static PassResult convertPractical(final boolean ungradedPassed) {
		if (ungradedPassed) {
			return PassResult.PASSED;
		} else {
			return PassResult.NOTPASSED;
		}
	}

	private static List<IResult> convert(final List<XmlExaminationResult> results, final IStudyRegulations regulations, final List<IResultType> resultTypes) {
		final List<IResult> result = new LinkedList<IResult>();
		for (final XmlExaminationResult xmlResult : results) {
			result.add(convert(xmlResult, regulations, resultTypes));
		}
		
		return result;
	}

	protected static List<IPerformanceType> convertPerformances(final String list, final List<IPerformanceType> types) {
		final List<IPerformanceType> result = new LinkedList<IPerformanceType>();
		if (list.trim().equals("")) {
			return result;
		}
		for (final String abbreviation : list.split(" ")) {
			final IPerformanceType type = PerformanceListUtil.valueOf(abbreviation, types);
			if (type == null) {
				throw new IllegalStateException("Unkown performance type (" + abbreviation + ")");
			}
			
			result.add(type);
		}
		
		return result;
	}

//	private List<ICourseType> convertCourseTypes(final String list,
//			final List<ICourseType> types) {
//		final List<ICourseType> result = new LinkedList<ICourseType>();
//		if (list.trim().equals("")) {
//			return result;
//		}
//		for (final String abbreviation : list.split(" ")) {
//			final ICourseType type = CourseListUtil.valueOf(abbreviation, types);
//			if (type == null) {
//				throw new IllegalStateException("Unkown course type (" + abbreviation + ")");
//			}
//			
//			result.add(type);
//		}
//		
//		return result;
//	}

//	private ICourse convert(final XmlCourse course, final IStudyRegulations regulations)
//	{
//		final ICourse result = factory.createCourse();
//		result.setName(course.getName());
//		result.setECTS(course.getECTS());
//		result.setHoursPerWeek(course.getHoursPerWeek());
//		result.setCourseNumber(course.getCourseNumber());
//		result.setExamNumber(course.getExamNumber());
//		result.setGradedPerformance(convertPerformances(course.getGradedPerformance(), regulations.getPerformanceTypes()));
//		result.setUngradedPerformance(convertPerformances(course.getUngradedPerformance(), regulations.getPerformanceTypes()));
//		result.setType(convertCourseTypes(course.getType(), regulations.getCourseTypes()));
//		return result;
//	}

	protected static List<ICourseType> convertCourseTypes(final List<XmlCourseTypeDescriptor> list) {
		final List<ICourseType> result = new LinkedList<ICourseType>();
		for (final XmlCourseTypeDescriptor descriptor : list) {
			GCCore.getFactory().createCourseType(result, descriptor.getId(), descriptor.getName());
		}
		
		return result;
	}
	
	protected static List<IPerformanceType> convertExamTypes(final List<XmlExaminationTypeDescriptor> list) {
		final List<IPerformanceType> result = new LinkedList<IPerformanceType>();
		for (final XmlExaminationTypeDescriptor descriptor : list) {
			GCCore.getFactory().createPerformanceType(result, descriptor.getId(), descriptor.getName());
		}
		
		return result;
	}
	

	@Override
	public void writeStudent(final IStudent student, final String saveFilename) {
		// TODO
	}

	@Override
	public String getName() {
		return "Student-XML-Plugin (StuX)";
	}

	@Override
	public String getVersion() {
		// TODO Auto-generated method stub
		return "0.9.0";
	}

	@Override
	public Map<String, List<String>> getLoadableFileExtensions() {
		return extensions;
	}

	@Override
	public Map<String, List<String>> getWriteableFileExtensions() {
		return extensions;
	}

	@Override
	public void setApplicationDirectory(final String directory) {
		root = directory;
	}

}
